package client;

import java.io.IOException;
import java.net.Socket;

import net.GameInfo;
import voting.Measure;
import client.view.TableFrame;

import commands.ChatCommand;
import commands.MeasureCommand;
import commands.VoteCommand;

import core.Game;
import core.GameParticipant;
import core.GameParticipantRegistry;

/**
 * Represents a client, which corresponds to a single Player. Client acts as an
 * observer of the subject GameServer (Observer pattern), but all
 * communication between the two passes through a ClientProxy. 
 * Client is the Controller in the Model-View-Controller pattern and the Client
 * of the Proxy pattern.
 */
public class Client extends net.ObjectSerializer implements GameParticipant, net.Reactor<net.StateUpdate>
{
	/**
	 * Creates a new Client to communicate with a GameServer.
	 * @param gameInfo
	 * @throws java.io.IOException
	 */
	public Client(GameInfo gameInfo) throws java.io.IOException
	{
		super(new Socket(gameInfo.host, gameInfo.port), ClientReactorProxy.class, ClientReactorProxy.NAME_MAPPER);
		GameParticipant old = GameParticipantRegistry.get();
		GameParticipantRegistry.put(this);
		this.gameInfo = gameInfo;
		game = new module.GameParser(gameInfo.module).parse(gameInfo.numPlayers, gameInfo.options);

		String playerName = connection.readLine();
		core.Player player = null;
		for (core.Player p : game.getPlayers())
			if (p.getName().equals(playerName)) {
				player = p;
				break;
			}
		this.player = player;

		dictionary.update(game);
		GameParticipantRegistry.put(old);
	}

	/**
	 * Listens for input from the GameServer while the GameServer is waiting for 
	 * more players to join.
	 */
	public void run()
	{
		// Register this thread as a maintainer of this GameParticipant
		GameParticipantRegistry.put(this);
		tf = new client.view.TableFrame(this);
		showMessage("Waiting for more players");
		try {
			for (commands.Command command : new module.GameObjectInitializer<core.Player>(getModule(), "client").init(player))
				sendCommand(command);
			reader.parse(connection.in());
		}
		catch (org.xml.sax.SAXException e) {e.printStackTrace();}
		catch (java.io.IOException e) {e.printStackTrace();}
	}

	/**
	 * Appends a received chat message to the chat panel on the TableFrame.
	 * @param message
	 */
	public void showMessage(final String message) {
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				tf.getChatPane().appendMessage(message);
			}
		});
	}

	/**
	 * Returns a Game object representing the current state of the game
	 */
	public Game getGame() {
		return game;
	}

	public core.Player getPlayer() {
		return player;
	}

	public void react(net.StateUpdate stateUpdate) {
		showMessage("<i>" + stateUpdate.getTimestamp() + "</i> " + stateUpdate.getMessage());
		stateUpdate.execute();
		dictionary.update(game);
	}

	/**
	 * Sends a message to the other participants in the game (a ChatCommand)
	 * or a vote (VoteCommand)
	 * @param message the message
	 */
	public void sendMessage(String message)
	{
		try {
			if (message.startsWith("/vote")) {
				if (!callVote(message)) 
					tf.getChatPane().appendMessage("Bad vote syntax");
			}
			else
				sendCommand(new ChatCommand(message));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void sendCommand(commands.Command command) throws IOException {
		writeBean(command);
	}

	public String getModule() {
		return gameInfo.module;
	}

	/**
	 * Starts a new vote, if the VotingManager is not currently holding a vote.
	 * @param m the measure to be voted on
	 * @return true if the VotingManager accepts the new vote
	 */
	public boolean callVote(Measure m)
	{
		// This needs to send more than just a reference to the measure
		// Also, how are we going to get this method to return true if the voting
		// manager accepts the vote?
		try {
			sendCommand(new MeasureCommand(m));
		} catch (IOException e) {e.printStackTrace();}

		return true;
	}

	/**
	 * Casts a vote for the current Measure.  Sends a VoteCommand.
	 * @param v the client's vote (yea = 1/nay = 0)
	 */
	public void vote(int v)
	{
		try {
			sendCommand(new VoteCommand(v));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Notifies the client that a new vote has been started.  The user is notified
	 * via the TableFrame interface.
	 * @param m the Measure being voted on
	 */
	public void notifyOfMeasure(Measure m) {
		currentMeasure = m;
		tf.getChatPane().appendMessage("New vote: " + m.toString());
	}

	public void notifyOfVote(core.Player player, int vote) {
	}

	/**
	 * Parses a string to extract vote/calling for vote
	 * Legal voting message syntax:
	 * /vote end_game
	 * /vote winner player_name
	 * /vote yes
	 * /vote no 
	 * @param message
	 * @return true if the string successfully parsed for voting info
	 */
	private boolean callVote(String message)
	{
		String[] parts = message.split(" ");
		if (parts.length < 2) return false;
			
		if ("end_game".equals(parts[1])) {
			callVote(new Measure(Measure.UNANIMOUS, Measure.ALL_QUORUM, new commands.EndGameCommand(), 30));
			return true;
		}
		else if ("winner".equals(parts[1])) {
			if (parts.length > 2)
				callVote(new Measure(Measure.TWO_THIRDS_MAJORITY, Measure.ALL_QUORUM, new commands.EndGameCommand(parts[2]), 30));
			return true;
		}
		else if ("yes".equals(parts[1])) {
			vote(1);
			return true;
		}
		else if ("no".equals(parts[1])) {
			vote(0);
			return true;
		}
			
		return false;
	}

	public void endGame(String winner)
	{
		String message = "The game is over.";
		
		if (winner != null)
			message += "  " + winner + " was the winner.";
		tf.getChatPane().appendMessage(message);
		tf.endGame();
		try {
			connection.socket().close();
		} catch (IOException e) {}
	}

	/** The JFrame for the client visualization of the game */
	private TableFrame tf = null;
	/** The current state of the game */
	private Game game;
	private final core.Player player;
	private final net.GameInfo gameInfo;
	/** The current measure being voted upon */
	private Measure currentMeasure;
}